#include <ncurses.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "../include/cityDrawer.h"
#include "../include/cityDefs.h"

//multiplicadores usados para agrandar el tamano de la ciudad.
#define HEIGHTMULT 2
#define WIDTHMULT 4

//ancho y alto de la ventana de log de pasajeros
#define LOGHEIGHT (19 * HEIGHTMULT)
#define LOGWIDTH 48

//posicion de las columnas de la ventana de log de pasajeros
#define POS_ESTADO 10
#define POS_BONDI 21
#define POS_LINEA 27
#define POS_ORIGEN 33
#define POS_DESTINO 40

//cuanto me tengo que mover en x y en y para avanzar a la proxima cuadra
#define MOVEX ((BLOCKWIDTH + STREETWIDTH)*WIDTHMULT)
#define MOVEY ((BLOCKHEIGHT + STREETWIDTH)*HEIGHTMULT)

//maxima cantidad de colores aceptados para lineas
#define MAX_COLORS 7
//macro que calcula el color que le corresponde a la linea x
#define LINE_COLOR(x) COLOR_PAIR((x-1)%7+1)

void drawBlock(int x, int y);
void drawEmpty(int x, int y);
void drawBondi(T_DataMap ** mapa, int x, int y);
void drawStop(T_DataMap ** mapa, int x, int y);
void drawTrafficLight(char tipo, int x, int y);

T_DataMap ** armaMapa(int largo, int ancho);
void printLog(WINDOW * logWin, passData_T logData);
WINDOW * createLogwin(int height, int width, int starty, int startx);
void scrollLog(WINDOW * logWin);

//mutexs para sincronizar al graficador con la ciudad
extern pthread_mutex_t mutexDrawer;
extern pthread_mutex_t mutexDrawer2;
extern pthread_cond_t mutexDrawAvailable;

static void exitSigUsr1(int sign);

//variable para ciclar infinitamente hasta que este seteada en 1
extern int exitFlag;

void *
cityDrawer(void * drawArg) {
	int x = WIDTHMULT, y = HEIGHTMULT, i, j;

	//levanto de la estructura pasada los valores de la ciudad
	int blockCantV = (*(drawT *)drawArg).blocksNumY;
	int blockCantH = (*(drawT *)drawArg).blocksNumY;
	//mapa de la ciudad el cual sera leido para graficar
	T_DataMap ** mapa = (*(drawT *)drawArg).map;
	//cola de la cual se sacaran los mensajes
	queueADT logQueue = (*(drawT *)drawArg).logQueue;

	//calculo el ancho y alto que tendra la ciudad graficamente
	int cityWidth = (MOVEX * blockCantH) + STREETWIDTH * WIDTHMULT;
	int cityHeight = (MOVEY * blockCantV) + STREETWIDTH * HEIGHTMULT;

	initscr();			/* Start curses mode */
	start_color();		/* Start color 	*/
	curs_set(0);		/* Erases cursor */

	//inicializo los colores para las lineas
	for (i = 1; i <= MAX_COLORS; i++)
		init_pair(i, i, COLOR_BLACK);

	attrset(COLOR_PAIR(7));

	for (i = 0; i < cityHeight - HEIGHTMULT; i+=MOVEY) {
		for (j = 0; j < cityWidth - WIDTHMULT; j+=MOVEX) {
			drawBlock(y+i+HEIGHTMULT, x+j+WIDTHMULT);
		}
	}

	refresh();

	WINDOW * logWin;

	//creo la ventana de log de pasajeros
	logWin = createLogwin(LOGHEIGHT, LOGWIDTH, 1, cityWidth + WIDTHMULT + 3);

	//handler de la señal ctrl+c para cerrar bien el programa
	static struct sigaction act;
	act.sa_handler = exitSigUsr1;
	sigemptyset(&(act.sa_mask));
	sigaddset(&(act.sa_mask), SIGUSR1);
	sigaction(SIGUSR1, &act, NULL);

	//espero a que la ciudad me deje avanzar
	pthread_mutex_lock(&mutexDrawer2);
	pthread_mutex_unlock(&mutexDrawer2);

	while(!exitFlag) {
		//si la ciudad esta ocupada, este mutex no me deja pasar
		pthread_mutex_lock(&mutexDrawer);

		//recorro el mapa que fue actulalizado por la ciudad
		//divido las dimensiones de la ciudad por los multiplicadores (ya que
		//estos son propios del graficador) y le sumo 2 por los bordes)
		for (i = 0; i < cityWidth/WIDTHMULT+2; i++) {
			for (j = 0; j < cityHeight/HEIGHTMULT+2; j++) {
				if ((mapa[i][j].tipo & BUS)) {
					drawBondi(mapa, i, j);
				}
				if (mapa[i][j].tipo == TRAFFIC_LIGHT_HOR) {
					drawTrafficLight(TRAFFIC_LIGHT_HOR, i, j);
				}
				if (mapa[i][j].tipo == TRAFFIC_LIGHT_VER) {
					drawTrafficLight(TRAFFIC_LIGHT_VER, i, j);
				}
				if (mapa[i][j].tipo & STOP) {
					drawStop(mapa, i, j);
				}
				if (mapa[i][j].tipo == EMPTY || mapa[i][j].tipo == STOP) {
					drawEmpty(i, j);
				}
			}
		}

		passData_T * logData;
		//si hay elementos para imprimir en el log, lo hago
		while(!isEmpty(logQueue)) {
			logData = getElem(logQueue);

			printLog(logWin, *logData);

			free(logData);
		}

		//aviso a la ciudad que puede volver a actualizar el mapa
		pthread_cond_signal(&mutexDrawAvailable);

		//refresco el grafico de la ciudad
		refresh();

		//espero a que la ciudad me de el ok para volver a graficar
		pthread_cond_wait(&mutexDrawAvailable, &mutexDrawer);

		pthread_mutex_unlock(&mutexDrawer);

	}

	//la ciudad pudo haber quedado en wait, asi que la despierto
	pthread_cond_signal(&mutexDrawAvailable);

	//borro la ventana
	delwin(logWin);

	endwin();

	pthread_exit(NULL);
}

void
drawBlock(int y, int x) {
	int i, j = 1;

	mvaddch(y, x, ACS_ULCORNER);
	for(i = 0; i < BLOCKWIDTH*(WIDTHMULT-1); i++) {
		addch(ACS_HLINE);
	}
	addch(ACS_URCORNER);

	for(j = 1; j <= BLOCKHEIGHT*(HEIGHTMULT-1); j++) {
		mvaddch(y+j, x, ACS_VLINE);
		mvaddch(y+j, x + BLOCKWIDTH*(WIDTHMULT-1) + 1, ACS_VLINE);
	}

	mvaddch(y + j, x, ACS_LLCORNER);
	for(i = 0; i < BLOCKWIDTH*(WIDTHMULT-1); i++) {
		addch(ACS_HLINE);
	}
	addch(ACS_LRCORNER);
}

void
drawEmpty(int x, int y) {
	int i, j;

	for (i = 0; i < WIDTHMULT; i++)
		for (j = 0; j < HEIGHTMULT; j++)
			mvaddch(y * HEIGHTMULT + j, x * WIDTHMULT + i, ' ');
}

void
drawBondi(T_DataMap ** mapa, int x, int y) {

	attron(LINE_COLOR(mapa[x][y].linea));
	mvaddch(y * HEIGHTMULT, x * WIDTHMULT + 0, ACS_ULCORNER);
	mvaddch(y * HEIGHTMULT, x * WIDTHMULT + 1, mapa[x][y].bondi / 10  + '0');
	mvaddch(y * HEIGHTMULT, x * WIDTHMULT + 2, mapa[x][y].bondi % 10  + '0');
	mvaddch(y * HEIGHTMULT, x * WIDTHMULT + 3, ACS_URCORNER);
	mvaddch(y * HEIGHTMULT + 1, x * WIDTHMULT + 0, ACS_LLCORNER);
	mvaddch(y * HEIGHTMULT + 1, x * WIDTHMULT + 1, mapa[x][y].pasAbord / 10  + '0');
	mvaddch(y * HEIGHTMULT + 1, x * WIDTHMULT + 2, mapa[x][y].pasAbord % 10  + '0');
	mvaddch(y * HEIGHTMULT + 1, x * WIDTHMULT + 3, ACS_LRCORNER);
	attroff(LINE_COLOR(mapa[x][y].linea));

}

void
drawTrafficLight(char tipo, int x, int y) {
	drawEmpty(x, y);

	if (tipo == TRAFFIC_LIGHT_VER) {
		mvaddch(y*HEIGHTMULT, x*WIDTHMULT+1, ACS_VLINE);
		mvaddch(y*HEIGHTMULT+1, x*WIDTHMULT+1, ACS_VLINE);
	}
	else {
		mvaddch(y*HEIGHTMULT+1, x*WIDTHMULT, ACS_HLINE);
		mvaddch(y*HEIGHTMULT+1, x*WIDTHMULT+1, ACS_HLINE);
		mvaddch(y*HEIGHTMULT+1, x*WIDTHMULT+2, ACS_HLINE);
		mvaddch(y*HEIGHTMULT+1, x*WIDTHMULT+3, ACS_HLINE);
	}
}

void
drawStop(T_DataMap ** mapa, int x, int y) {
	int xNew = 1, yNew;

	yNew = ((y+(y-1)/3)%2) == 0 ? 1 : 0;

	switch(mapa[x][y].way){
		case NORTH: xNew = WIDTHMULT; break;
		case SOUTH: xNew = -2; break;
		case EAST: yNew = HEIGHTMULT;break;
		case WEST: yNew = -1;break;
		default: break;
	}

	mvaddch(y*HEIGHTMULT + yNew, x*WIDTHMULT + xNew,
		mapa[x][y].numParAbs/10  + '0');
	mvaddch(y*HEIGHTMULT + yNew, x*WIDTHMULT + xNew + 1,
		mapa[x][y].numParAbs%10  + '0');
}

//scrolleo el log, imprimo y refresco
void
printLog(WINDOW * logWin, passData_T logData) {
	scrollLog(logWin);
	wattron(logWin, LINE_COLOR(logData.linea));
	char * status[3] = {"Esperando", "Bajando", "Subiendo"};

	mvwprintw(logWin, logWin->_maxy-1, 1, "%d%03d", logData.linea, logData.numeroPas);
	mvwprintw(logWin, logWin->_maxy-1, POS_ESTADO, "%s", status[(int)logData.status]);
	if (logData.bondi != 0)
		mvwprintw(logWin, logWin->_maxy-1, POS_BONDI, "%02d", logData.bondi);
	else
		mvwprintw(logWin, logWin->_maxy-1, POS_BONDI, "%c%c", '-', '-');
	mvwprintw(logWin, logWin->_maxy-1, POS_LINEA, "%02d", logData.linea);
	mvwprintw(logWin, logWin->_maxy-1, POS_ORIGEN, "%02d", logData.paradaOr);
	mvwprintw(logWin, logWin->_maxy-1, POS_DESTINO, "%02d", logData.paradaDest);

	wattroff(logWin, LINE_COLOR(logData.linea));
	wrefresh(logWin);
}

static void
exitSigUsr1(int sign) {
	exitFlag = 1;
}

void
createLogShape(WINDOW * logWin) {
	//hago el borde de la ventana de log y el titulo de la misma
	box(logWin, 0 , 0);

	int i;
	mvwaddch(logWin, 2, 0, ACS_LTEE);
	mvwaddch(logWin, 2, logWin->_maxx, ACS_RTEE);
	for (i = 1; i < logWin->_maxx; i++)
		mvwaddch(logWin, 2, i, ACS_HLINE);

	mvwprintw(logWin, 0, logWin->_maxx/2 - 11, "Seguimiento de pasajeros");
	mvwprintw(logWin, 1, 1, "Pasajero");
	mvwaddch(logWin, 0, POS_ESTADO-1, ACS_TTEE);
	mvwaddch(logWin, 1, POS_ESTADO-1, ACS_VLINE);
	mvwaddch(logWin, 2, POS_ESTADO-1, ACS_PLUS);
	mvwaddch(logWin, logWin->_maxy-1, POS_ESTADO-1, ACS_VLINE);
	mvwaddch(logWin, logWin->_maxy, POS_ESTADO-1, ACS_BTEE);
	mvwprintw(logWin, 1, POS_ESTADO, "Estado    ");
	mvwaddch(logWin, 1, POS_BONDI-1, ACS_VLINE);
	mvwaddch(logWin, 2, POS_BONDI-1, ACS_PLUS);
	mvwaddch(logWin, logWin->_maxy-1, POS_BONDI-1, ACS_VLINE);
	mvwaddch(logWin, logWin->_maxy, POS_BONDI-1, ACS_BTEE);
	mvwprintw(logWin, 1, POS_BONDI, "Bondi");
	mvwaddch(logWin, 1, POS_LINEA-1, ACS_VLINE);
	mvwaddch(logWin, 2, POS_LINEA-1, ACS_PLUS);
	mvwaddch(logWin, logWin->_maxy-1, POS_LINEA-1, ACS_VLINE);
	mvwaddch(logWin, logWin->_maxy, POS_LINEA-1, ACS_BTEE);
	mvwprintw(logWin, 1, POS_LINEA, "Linea");
	mvwaddch(logWin, 1, POS_ORIGEN-1, ACS_VLINE);
	mvwaddch(logWin, 2, POS_ORIGEN-1, ACS_PLUS);
	mvwaddch(logWin, logWin->_maxy-1, POS_ORIGEN-1, ACS_VLINE);
	mvwaddch(logWin, logWin->_maxy, POS_ORIGEN-1, ACS_BTEE);
	mvwprintw(logWin, 1, POS_ORIGEN, "Origen");
	mvwaddch(logWin, 0, POS_DESTINO-1, ACS_TTEE);
	mvwaddch(logWin, 1, POS_DESTINO-1, ACS_VLINE);
	mvwaddch(logWin, 2, POS_DESTINO-1, ACS_PLUS);
	mvwaddch(logWin, logWin->_maxy-1, POS_DESTINO-1, ACS_VLINE);
	mvwaddch(logWin, logWin->_maxy, POS_DESTINO-1, ACS_BTEE);
	mvwprintw(logWin, 1, POS_DESTINO, "Destino");
}

WINDOW *
createLogwin(int height, int width, int starty, int startx)
{
	WINDOW * logWin;

	logWin = newwin(height, width, starty, startx);

	createLogShape(logWin);

	int i;
	for (i = 3; i < logWin->_maxy-1; i++) {
		mvwaddch(logWin, i, POS_ESTADO-1, ACS_VLINE);
		mvwaddch(logWin, i, POS_BONDI-1, ACS_VLINE);
		mvwaddch(logWin, i, POS_LINEA-1, ACS_VLINE);
		mvwaddch(logWin, i, POS_ORIGEN-1, ACS_VLINE);
		mvwaddch(logWin, i, POS_DESTINO-1, ACS_VLINE);
	}

	wrefresh(logWin);

	//seteo la ventana como scrolleable
	scrollok(logWin, TRUE);

	return logWin;
}

void
scrollLog(WINDOW * logWin) {
	//borro el borde, para que el scroll no lo corra
	wborder(logWin, ' ', ' ', ' ',' ',' ',' ',' ',' ');
	scroll(logWin);

	//redibujo el borde y el titulo
	createLogShape(logWin);
}

